package connection

import (
	"abdis/config"
	"abdis/lib/sync/wait"
	"abdis/lib/utils"
	"net"
	"sync"
	"time"
)

const (
	// flagSlave means this a connection with slave
	flagSlave = uint64(1 << iota)
	// flagMaster means this a connection with master
	flagMaster
	// flagMulti means this connection is within a transaction
	flagMulti
)

type Connection struct {
	conn net.Conn

	// wait until finish sending data, used for graceful shutdown
	sendingData wait.Wait

	mu    sync.Mutex
	state uint64

	authed bool

	// 事务相关
	queue    [][][]byte
	txErrors []error
	watching map[string]uint32

	selectedDB int
}

func (c *Connection) Close() error {
	c.sendingData.WaitWithTimeout(10 * time.Second)
	_ = c.conn.Close()
	c.queue = nil
	c.watching = nil
	c.txErrors = nil
	c.selectedDB = 0
	return nil
}

func MakeConnection(conn net.Conn) *Connection {
	return &Connection{
		conn:   conn,
		authed: utils.CheckAuth(config.Properties.RequirePass, ""),
	}
}

func (c *Connection) RemoteAddr() string {
	return c.conn.RemoteAddr().String()
}

func (c *Connection) Name() string {
	if c.conn != nil {
		return c.RemoteAddr()
	}
	return ""
}

func (c *Connection) Write(b []byte) (int, error) {
	if len(b) == 0 {
		return 0, nil
	}
	c.sendingData.Add(1)
	defer c.sendingData.Done()

	// 这一步向客户端写数据，消耗时间
	return c.conn.Write(b)
}

func (c *Connection) GetDBIndex() int {
	return c.selectedDB
}

func (c *Connection) SelectDB(num int) {
	c.selectedDB = num
}

func (c *Connection) SetMultiState(state bool) {
	if !state {
		c.state &= ^flagMulti
		c.queue = nil
		c.watching = nil
		return
	}
	c.state |= flagMulti
}

func (c *Connection) InMultiState() bool {
	return c.state&flagMulti > 0
}

func (c *Connection) AddTxError(err error) {
	c.txErrors = append(c.txErrors, err)
}

func (c *Connection) GetTxErrors() []error {
	return c.txErrors
}

func (c *Connection) EnqueueCmd(cmdLine [][]byte) {
	c.queue = append(c.queue, cmdLine)
}

func (c *Connection) ClearQueuedCmds() {
	c.queue = nil
}

func (c *Connection) GetQueuedCmdLine() [][][]byte {
	return c.queue
}

// GetWatching returns watching keys and their version code when started watching
func (c *Connection) GetWatching() map[string]uint32 {
	if c.watching == nil {
		c.watching = make(map[string]uint32)
	}
	return c.watching
}

func (c *Connection) IsAuthed() bool {
	return c.authed
}

func (c *Connection) Auth(p string) {
	c.authed = utils.CheckAuth(config.Properties.RequirePass, p)
}
